package com.gmrz.webauthn.handler;

import com.gmrz.service.challengestore.ChallengeStoreService;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.crypto.CryptoEngine;
import com.gmrz.uaf.db.UAFDAOFactory;
import com.gmrz.uaf.db.UAFDBConnectionMgr;
import com.gmrz.uaf.servlet.v1.handler.UAFBaseHandler;
import com.gmrz.uaf.servlet.v1.validation.PayloadValidationException;
import com.gmrz.uaf.servlet.v1.validation.RestValidator;
import com.gmrz.util.Convert;
import com.gmrz.util.Strings;
import com.gmrz.util.db.DBUtil;
import com.gmrz.webauthn.common.WebAuthnConstants;
import com.gmrz.webauthn.db.dao.WebAuthnAuthenticatorsDAO;
import com.gmrz.webauthn.protocol.v1.json.WebAuthnSchemaBuilder;
import com.gmrz.webauthn.protocol.v1.schema.*;
import com.gmrz.webauthn.request.WebAuthnRegContext;
import com.gmrz.webauthn.request.WebAuthnRequestContext;
import com.gmrz.webauthn.request.WebAuthnReqPayload;
import com.gmrz.webauthn.request.WebAuthnRespPayload;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.security.SecureRandom;
import java.sql.Connection;
import java.util.*;

public class WebRegRequestHandler extends UAFBaseHandler {
    private static final Logger LOG = LogManager.getLogger(WebRegRequestHandler.class);
    private CryptoEngine cryptoEngine;

    @Inject
    public void setCryptoEngine(CryptoEngine cryptoEngine) {
        this.cryptoEngine = cryptoEngine;
    }


    public String regInit(String request) {

        LOG.info("[/reg/receive][begin]");
        Gson gson = WebAuthnSchemaBuilder.getGson();
        WebAuthnRespPayload<WebAuthnRequestContext> respPayload = new WebAuthnRespPayload<WebAuthnRequestContext>();
        Type reqType = new TypeToken<WebAuthnReqPayload<WebAuthnRegContext>>() {
        }.getType();

        String response;
        Connection conn = null;
        try {

            WebAuthnReqPayload<WebAuthnRegContext> reqPayload = gson.fromJson(request, reqType);
            //记录日志
            logRequest(request, reqPayload.getContext().getTransNo(), reqPayload.getContext().getUserName(),
                    reqPayload.getContext().getAppID(), reqPayload.getContext().getAuthType(), reqPayload.getContext().getTransType(), Constants.Method.REG_INIT);

            //验证请求参数
            validate(reqPayload);

            String appID = reqPayload.getContext().getAppID();
            String authType = reqPayload.getContext().getAuthType();
            String transType = reqPayload.getContext().getTransType();


            String attachment = serverConfig.getPolicyConfig(appID,authType,transType,WebAuthnConstants.WEBAUTHN_AUTHENTICATOR_SELECTION_CRITERIA_AUTHENTICATOR_ATTACHMENT);
            String attestation = serverConfig.getPolicyConfig(appID,authType,transType,WebAuthnConstants.WEBAUTHN_AUTHENTICATOR_SELECTION_CRITERIA_AUTHENTICATOR_ATTESTATION);
            String anonymous = serverConfig.getPolicyConfig(appID,authType,transType,WebAuthnConstants.WEBAUTHN_AUTH_ANONYMOUS);
            String strictmode = serverConfig.getPolicyConfig(appID,authType,transType,WebAuthnConstants.WEBAUTHN_AUTH_STRICTMODE);
            String residentkey = serverConfig.getPolicyConfig(appID,authType,transType,WebAuthnConstants.WEBAUTHN_AUTHENTICATOR_SELECTION_CRITERIA_REQUIRE_RESIDENTKEY);
            String verification = serverConfig.getPolicyConfig(appID,authType,transType,WebAuthnConstants.WEBAUTHN_AUTHENTICATOR_SELECTION_CRITERIA_USER_VERIFICATION);
            String timeout = serverConfig.getPolicyConfig(appID,authType,transType,WebAuthnConstants.WEBAUTHN_AUTHENTICATOR_TIME_OUT);
            String extensions = serverConfig.getPolicyConfig(appID,authType,transType,WebAuthnConstants.WEBAUTHN_AUTHENTICATOR_EXTENSIONS);


            String rpID = WebAuthnConstants.getTenantConfigContent(WebAuthnConstants.WEBAUTHN_AUTHENTICATOR_RPID, reqPayload.getContext().getAppID());
            String rpName = WebAuthnConstants.getTenantConfigContent(WebAuthnConstants.WEBAUTHN_AUTHENTICATOR_RPNAME, reqPayload.getContext().getAppID());


            PublicKeyCredentialCreationOptions options = GuiceUtil.getProcessorInjector().getInstance(PublicKeyCredentialCreationOptions.class);
            if(Strings.isNotBlank(attestation)) {
                options.setAttestation(AttestationConveyancePreference.forName(attestation));
            }
            AuthenticatorSelectionCriteria authenticatorSelectionCriteria = GuiceUtil.getProcessorInjector().getInstance(AuthenticatorSelectionCriteria.class);
            if(Strings.isNotBlank(attachment)) {
                authenticatorSelectionCriteria.setAuthenticatorAttachment(AuthenticatorAttachment.forName(attachment));
            }
            if(Strings.isNotBlank(residentkey)) {
                authenticatorSelectionCriteria.setRequireResidentKey(Boolean.parseBoolean(residentkey));
            }
            if(Strings.isNotBlank(verification)) {
                authenticatorSelectionCriteria.setUserVerification(UserVerificationRequirement.forName(verification));
            }
            options.setAuthenticatorSelection(authenticatorSelectionCriteria);
            if(Strings.isNotBlank(timeout)) {
                int timeOut = Integer.parseInt(timeout);
                options.setTimeout(timeOut);//超时设置
            }
            PublicKeyCredentialRpEntity publicKeyCredentialRpEntity = GuiceUtil.getProcessorInjector().getInstance(PublicKeyCredentialRpEntity.class);
            publicKeyCredentialRpEntity.setId(rpID);
            publicKeyCredentialRpEntity.setName(rpName);
            options.setRp(publicKeyCredentialRpEntity);

            PublicKeyCredentialUserEntity publicKeyCredentialUserEntity = GuiceUtil.getProcessorInjector().getInstance(PublicKeyCredentialUserEntity.class);
            publicKeyCredentialUserEntity.setId(Convert.toBase64(getNextRandomBytes(32)));
            publicKeyCredentialUserEntity.setName(reqPayload.getContext().getUserName());
            publicKeyCredentialUserEntity.setDisplayName(reqPayload.getContext().getUserName());
            options.setUser(publicKeyCredentialUserEntity);

            options.setCryptoParameters(get());//支持的算法

            String hashedUserName = generateHashedUserName(reqPayload.getContext().getUserName(), reqPayload.getContext().getRf1(), reqPayload.getContext().getRf2());
            String deviceID = null;
            if(null != reqPayload.getContext().getDevices()){
                deviceID = reqPayload.getContext().getDevices().getDeviceID();
            }
            conn = UAFDBConnectionMgr.getConnection();
            WebAuthnAuthenticatorsDAO webauthnAuthenticatorsDAO = UAFDAOFactory.createWebAuthnAuthenticatorsDAO(conn);
            List<WebAuthenticator> authenticators = webauthnAuthenticatorsDAO.find(reqPayload.getContext().getAppID(), hashedUserName,reqPayload.getContext().getAuthType(),reqPayload.getContext().getTransType(),deviceID);

            if (!authenticators.isEmpty()) {
                List<PublicKeyCredentialDescriptor> excludeCredentials = new ArrayList<PublicKeyCredentialDescriptor>();

                for (WebAuthenticator webAuthenticator : authenticators) {
                    PublicKeyCredentialDescriptor publicKeyCredentialDescriptor = new PublicKeyCredentialDescriptor();
                    publicKeyCredentialDescriptor.setId(webAuthenticator.getCredentialId());
                    publicKeyCredentialDescriptor.setType(PublicKeyCredentialType.forName(webAuthenticator.getType()));
                    excludeCredentials.add(publicKeyCredentialDescriptor);
                }
                options.setExcludeCredentials(excludeCredentials);
            }

            if(Strings.isNotBlank(extensions)) {
                Map extMap = gson.fromJson(extensions, HashMap.class);
                options.setExtensions(extMap);
            }


            //验证应用ID是否存在
//            Set<String> facetIDs = this.serverConfig.getFacetIDs(reqPayload.getContext().getAppID());
//            if (!facetIDs.contains(reqPayload.getContext().getRpId())) {
//                throw new DAOException(UAFErrorCode.PROTOCOL_FACETID_VALIDATION_FAILED);
//            }

            //设置挑战值，并将挑战值放入缓存以及失效时间
            String challenge = Convert.toBase64(getNextRandomBytes(32));
            ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
            challengeStoreService.setRedisValue(challenge, this.getServerConfig().getChallengeValiditySeconds(), challenge);
            options.setChallenge(challenge);

            response = buildWebAuthnResponse(gson, respPayload, options);

        } catch (Exception ex) {
            LOG.error("[/reg/receive] ERROR", ex);
            response = buildWebAuthnRespFromException(respPayload, ex);
        }finally {
            DBUtil.close(conn);
        }
        LOG.info("[/reg/receive][end]");
        return response;
    }


    public List<PublicKeyCredentialParameters> get() {
        List<PublicKeyCredentialParameters> publicKeyCredentialParametersList = new ArrayList<PublicKeyCredentialParameters>();

        for (SignAlgorithmIdentifier signAlgorithmIdentifier : SignAlgorithmIdentifier.values()) {
            PublicKeyCredentialParameters publicKeyCredentialParameters = new PublicKeyCredentialParameters();
            publicKeyCredentialParameters.setType(PublicKeyCredentialType.PUBLIC_KEY_CREDENTIAL);
            publicKeyCredentialParameters.setAlgorithm(signAlgorithmIdentifier);
            publicKeyCredentialParametersList.add(publicKeyCredentialParameters);
        }

        return publicKeyCredentialParametersList;
    }


    public byte[] getNextRandomBytes(int length) {
        SecureRandom secureRandom = new SecureRandom();
        byte[] retval = new byte[length];
        secureRandom.nextBytes(retval);
        return retval;
    }


    private void validate(WebAuthnReqPayload<WebAuthnRegContext> reqPayload) throws PayloadValidationException, UnsupportedEncodingException {
        if ((reqPayload == null) || (reqPayload.getContext() == null)) {
            throw new PayloadValidationException("context", RestValidator.VALIDATION_ERROR.EMPTY);
        }
        RestValidator.validate("userName", reqPayload.getContext().getUserName());
        RestValidator.validate("appID", reqPayload.getContext().getAppID());
        //RestValidator.validate("deviceID", reqPayload.getContext().getDevices().getDeviceID());
        RestValidator.validate("authType", reqPayload.getContext().getAuthType());
        RestValidator.validate("transType", reqPayload.getContext().getTransType());
        RestValidator.validate("transNo", reqPayload.getContext().getTransNo());
        RestValidator.validate("rf1", reqPayload.getContext().getRf1());
        RestValidator.validate("rf2", reqPayload.getContext().getRf2());
        RestValidator.validate("protocol", reqPayload.getContext().getProtocol());
    }
}
